﻿using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Sustainalytics.Caching
{
    public class AppRedisCache : IAppRedisCache
    {
        private static ConnectionMultiplexer redisServerConnection;
        private IDatabase repository;
        private static IServer _redisServer;
        private static bool _allowAdmin;
        private static ISubscriber subscriber;

        public AppRedisCache(string connectionString, int database, bool allowAdmin = false)
        {
            if (string.IsNullOrEmpty(connectionString))
                throw new ArgumentException("Connection string cannot be empty");

            _allowAdmin = allowAdmin;

            redisServerConnection = ConnectionMultiplexer.Connect(connectionString + (_allowAdmin ? ",allowAdmin=true" : string.Empty));

            redisServerConnection.ConnectionFailed += redisServer_ConnectionFailed;

            repository = redisServerConnection.GetDatabase(database);

            _redisServer = redisServerConnection.GetServer(connectionString, 6379);
            
            subscriber = redisServerConnection.GetSubscriber();
        }

        public AppRedisCache(string connectionString, int database, int timeoutMilliseconds, bool allowAdmin = false)
        {
            if (string.IsNullOrEmpty(connectionString))
                throw new ArgumentException("Connection string cannot be empty");

            _allowAdmin = allowAdmin;


            var cfg = ConfigurationOptions.Parse(connectionString, true);
            if (timeoutMilliseconds > 0)
                cfg.SyncTimeout = timeoutMilliseconds;

            cfg.AllowAdmin = _allowAdmin;
                
            redisServerConnection = ConnectionMultiplexer.Connect(cfg);
            
            redisServerConnection.ConnectionFailed += redisServer_ConnectionFailed;
                
            repository = redisServerConnection.GetDatabase(database);

            _redisServer = redisServerConnection.GetServer(connectionString, 6379);

            subscriber = redisServerConnection.GetSubscriber();
        }

        public static IAppCache GetLevel2CacheClient(string connectionString, int database)
        {
            AppRedisCache redisClient = new AppRedisCache(connectionString, database);

            return redisClient;
        }

        
        void redisServer_ConnectionFailed(object sender, ConnectionFailedEventArgs e)
        {
            throw new ArgumentException("Connection to the Cache Server Failed");

        }

        public void Set(string key, string value)
        {
            if (!string.IsNullOrEmpty(key))
            {
                LogWriteToRedis(repository.StringSet(key, value), key, value);
            }
            else
            {
                throw new CacheItemKeyNullOrEmptyException();
            }

        }

        public void Set(string key, IEnumerable<string> values)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new CacheItemKeyNullOrEmptyException();
            }

            if (values == null)
            {
                return;
            }

            foreach (string val in values)
            {
                LogWriteToRedis(repository.SetAdd(key, val), key, val);
            }
        }

        private void LogWriteToRedis(bool success, string key, string value)
        {
            if(!success)
                CommonEventSource.Log.ErrorWritingToRedis(key, value);
        }

        private void LogRemoveFromRedis(bool success, string key)
        {
            if (!success)
                CommonEventSource.Log.ErrorRemoveFromRedis(key);
        }


        public void Add2Set(string key, string value)
        {
            if (string.IsNullOrEmpty(key))            
                throw new CacheItemKeyNullOrEmptyException();


            if (value != null)
            {
                LogWriteToRedis(repository.SetAdd(key, value), key, value);
            }
        }

        public string GetString(string key)
        {
            string value = string.Empty;

            if (!string.IsNullOrEmpty(key))
            {
                value = repository.StringGet(key);
            }

            return value;
        }

        public IEnumerable<string> GetSet(string key)
        {
            List<string> redisOutput = new List<string>();

            if (!string.IsNullOrEmpty(key))
            {
                RedisValue[] values = repository.SetMembers(key);

                if (values != null)
                {
                    foreach (RedisValue value in values)
                    {
                        redisOutput.Add(value);
                    }
                }
            }
            else
            {
                throw new CacheItemKeyNullOrEmptyException();
            }

            return redisOutput;
        }

        public void RemoveAll()
        {
            CommonEventSource.Log.RedisFlushDatabase(repository.Database);
            _redisServer.FlushDatabase(repository.Database);
        }

        public void Remove(string key)
        {
            if (!string.IsNullOrEmpty(key))
                LogRemoveFromRedis(repository.KeyDelete(key), key);
            else
            {
                throw new CacheItemKeyNullOrEmptyException();
            }
        }

        public void Remove(string key, string value)
        {
            if (!string.IsNullOrEmpty(key))
                LogRemoveValueFromRedis(repository.SetRemove(key, value), key, value);
            else
            {
                throw new CacheItemKeyNullOrEmptyException();
            }
        }

        private void LogRemoveValueFromRedis(bool success, string key, string value)
        {
            if (!success)
                CommonEventSource.Log.ErrorRemoveValueFromRedis(key, value);
        }

        public void Clear()
        {
        }

        public void Clear(int database)
        {
            _redisServer.FlushDatabase(database);
        }

        public void Subscribe(Action<AppCacheItemAction> action)
        {
        }

        public async void Subscribe(string channel, Action<Object> action)
        {
            Task sub = subscriber.SubscribeAsync(channel, (_channel, message) =>
            {
                action(message);
            });

            await sub;
        }
        public void Publish(string channel, string message)
        {
            subscriber.Publish(channel, message);
        }
        public void CloseAll()
        {
            redisServerConnection.Close(false);
            redisServerConnection.Dispose();
        }
        public List<string> BuildMemoryCache()
        {
            return _redisServer.Keys(0, "*").Select(key => key.ToString()).ToList();
        }
    }
}
